/*
 * =====================================================================================
 *
 *       Filename:  pageInfoGetter.cpp
 *
 *    Description:  
 *
 *        Version:  1.0
 *        Created:  2009-2-26 10:55:02
 *       Revision:  none
 *       Compiler:  gcc
 *
 *         Author:  Fang Dongheng (fndisme), fndisme@163.com
 *        Company:  Artcoding, Shanghai, China
 *
 * =====================================================================================
 */
#include "stdafx.h"
#include <algorithm>
#include <sqlite3.h>
#include <boost/algorithm/string/predicate.hpp>
#include <boost/algorithm/string/erase.hpp>
#include "pageInfoGetter.hpp"
#include "utility.h"

namespace Utility {
    namespace fs = boost::filesystem ;
    namespace bs = boost::algorithm ;


    bool page_info_getter::get_keys(const std::string& page,
                                    property_keys_type& keys) {
        try {
            property_keys_type k = ::get_keys(page_map[page]) ;
            std::swap(k, keys) ;
            return true ;
        } catch(...) {
            return false ;
        }
    }

    bool page_info_getter::get_keys(const std::string& page,
                                    const std::string& group,
                                    property_keys_type& keys) {
        try {
            property_keys_type k = ::get_keys(page_map[page], group) ;
            std::swap(k, keys) ;
            return true ;
        } catch(...) {
            return false ;
        }
    }
    
    void page_info_getter::update_child_path(const fs::path& child_path,
                                             sqlite3* db,
                                             std::time_t db_last_time,
                                             bool need_create ) {
        fs::directory_iterator end_iter ;
        for(fs::directory_iterator dir_itr(child_path) ;
            dir_itr != end_iter ;
            ++dir_itr) {
            if(fs::is_directory(dir_itr->status())) {
                update_child_path(dir_itr->path(), db, db_last_time, need_create) ;
            } else if(fs::is_regular_file(dir_itr->status())) {

                std::string key_name = dir_itr->path().filename() ;
                if(bs::iends_with(key_name, cs_extend)) {
                    const std::string& full_name = dir_itr->path().file_string() ;
                    std::string info = read_from_file(full_name) ;
                    std::time_t latime = fs::last_write_time(dir_itr->path()) ;
                    bs::erase_last(key_name, cs_extend) ;
                    if(need_create || db_last_time <  latime) {
                        bool ok = update_info(db, key_name, info) ;
                        if(!ok) {
                            throw std::runtime_error("not right good format page info file.") ;
                        }
                    }
                }
            }
        }
    }
    
    page_info_getter& page_info_getter::Instance() {
        static page_info_getter instance ;
        return instance ;
    }

    void page_info_getter::add_pages_from_db(const char* db_name) {
        if(!fs::exists(fs::path(db_name))) return ;
        sqlite3* db ;
        char* zErr ;
        sqlite3_stmt* stmt ;
        const char* sqlcmd ;
        const char* tail ;
        int rc ;

        rc = sqlite3_open(db_name, &db) ;
        if(rc) {
            std::runtime_error e(sqlite3_errmsg(db)) ;
            sqlite3_close(db) ;
            throw e ;
        }

        sqlcmd = "select * from pages ;" ;
        rc = sqlite3_prepare(db, sqlcmd, strlen(sqlcmd), &stmt, &tail) ;
        if(rc != SQLITE_OK) {
            std::runtime_error e(sqlite3_errmsg(db)) ;
            sqlite3_close(db) ;
            throw e ;            
        }

        int ncols = sqlite3_column_count(stmt) ;
        if(ncols != 2) {
            sqlite3_finalize(stmt) ;
            sqlite3_close(db) ;
            throw std::runtime_error("bad infos") ;
        }

        rc = sqlite3_step(stmt) ;

        while(rc == SQLITE_ROW) {
            const char* page_name = reinterpret_cast<const char*>(sqlite3_column_text(stmt, 0)) ;
            const char* info = reinterpret_cast<const char*>(sqlite3_column_text(stmt, 1)) ;
            m_page_names.insert(page_name) ;
            add_page_from_string(info, page_name) ;
            rc = sqlite3_step(stmt) ;
        }
        
        sqlite3_finalize(stmt) ;
        sqlite3_close(db) ;
    }

    bool page_info_getter::has_page(const std::string& name) const {
        return m_page_names.count(name) == 1 ;
    }


    bool page_info_getter::create_db_from_directory(const char* dir,
                                                    const char* dbname) {
        fs::path full_path = fs::system_complete(fs::path(dir)) ;
        if(!fs::exists(full_path) || !fs::is_directory(full_path)) return false ;
        sqlite3* db ;
        char* zErr ;
        int rc ;
        const char* sql ;
        bool need_create = false ;
        if(!fs::exists(fs::path(dbname))) need_create = true ;
           
        rc = sqlite3_open(dbname, &db) ;
        if(rc) {
            sqlite3_close(db) ;
            return false ;
        }

        if(need_create) {
            const char* sqlcmd = "create table pages(name text primary key, info text)" ;
            rc = sqlite3_exec(db, sqlcmd, NULL, NULL, &zErr) ;
            if(rc != SQLITE_OK) {
                if(zErr != NULL) {
                    // FIXME error
                    sqlite3_free(zErr) ;
                    sqlite3_close(db) ;
                    return false ;
                }
            }
        }

        std::time_t db_last_time = fs::last_write_time(fs::path(dbname)) ;
        try {
            update_child_path(full_path, db, db_last_time, need_create) ;
            /*
              fs::directory_iterator end_iter ;
              for(fs::directory_iterator dir_itr(full_path) ;
              dir_itr != end_iter ;
              ++dir_itr) {
              try {
              std::string file_name = dir_itr->path().filename() ;
              if(bs::iends_with(file_name, cs_extend)) {
              std::string info = read_from_file(file_name) ;
              std::time_t latime = fs::last_write_time(dir_itr->path()) ;
              bs::erase_last(file_name, cs_extend) ;
              if(need_create || db_last_time <  latime) {
              bool ok = update_info(db, file_name, info) ;
              if(!ok) {
              sqlite3_close(db) ;
              return false ;
              }
              }
              }
            */
        } catch(const std::exception& e) {
            // FIXME about error
            std::string minfo = e.what() ;
            sqlite3_close(db) ;
            return false ;
        }
    
        sqlite3_close(db) ;
        return true ;
        
    }

    bool page_info_getter::update_info(sqlite3* db,
                                       const std::string& key,
                                       const std::string& info) {
        int rc ;
        char* zErr ;
        char* sqlcmd = sqlite3_mprintf("insert into pages values('%q', '%q')",
                                       key.c_str(), info.c_str()) ;
        rc = sqlite3_exec(db, sqlcmd, NULL, NULL, &zErr) ;
        if(rc != SQLITE_OK) {
            if(zErr) {
                sqlite3_free(zErr) ;
                sqlite3_free(sqlcmd) ;
                if(rc != SQLITE_CONSTRAINT) {  
                    return false ;
                } else {
                    sqlcmd = sqlite3_mprintf("update pages set info = '%q'"
                                             " where name = '%q'",
                                             info.c_str(),
                                             key.c_str()) ;
                    rc = sqlite3_exec(db, sqlcmd, NULL, NULL, &zErr) ;
                    if(rc != SQLITE_OK) {
                        sqlite3_free(zErr) ;
                        sqlite3_free(sqlcmd) ;
                        
                        return false ;
                    }   
                }    
                                
            }
                                
        }
        sqlite3_free(sqlcmd) ;
        return true ;
    }
    

    bool page_info_getter::update_db(const std::string& page,
                                     const char* db_name) {
        page_map_type::iterator info = page_map.find(page) ;
        if(info == page_map.end()) return false ;
        std::string info_data = write_property_map_to_string((*info).second) ;
        sqlite3* db ;
        int rc ;
        rc = sqlite3_open(db_name, &db) ;
        if(rc) {
            sqlite3_close(db) ;
            return false ;
        }
        char* sqlcmd = sqlite3_mprintf("update pages set info = '%q' where name = '%q'",
                                       info_data.c_str(), page.c_str()) ;
        char* zErr ;
        rc = sqlite3_exec(db, sqlcmd, NULL, NULL, &zErr) ;
        if(rc != SQLITE_OK) {
            if(zErr) sqlite3_free(zErr) ;
            sqlite3_free(sqlcmd) ;
            sqlite3_close(db) ;
            return false ;
        }
        sqlite3_free(sqlcmd) ;
        sqlite3_close(db) ;
        return true ;
    }

    bool page_info_getter::write_page_info_to_file(const std::string& file_name,
                                                   const std::string& page) {
        page_map_type::iterator info = page_map.find(page) ;
        if(info == page_map.end())
            return false ;
        try {
            write_property_map_to_file((*info).second, file_name.c_str()) ;
        } catch(const char* e) {
            std::string what(e) ;
            return false ;
        }
        return true ;
    }

}


